﻿using Callback;
using Newtonsoft.Json;
using Spire.Doc;
using Spire.Doc.Documents;
using Spire.Doc.Fields;
using Spire.Doc.Formatting;
using Spire.Doc.Interface;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;

namespace OfficeHelper
{
	/*
		通过标记读取配置
		通过配置读取数据
		通过配置替换数据
		 
	*/
	public class WordHelper
	{
        // 作用是：拿到DocumentObject对象，将其变成文本
		private static Func<DocumentObject, DocumentObjectConfig, WordTemplateConfig, object> defaultProcessFunc = (obj, docObjConfig, templateConfig) =>
		{
			switch(docObjConfig.DocumentObjectType)
			{
				case DocumentObjectType.Paragraph:
					{
						Paragraph para = obj as Paragraph;
						return DocConvert.DocToHTML(para);
                    }
                case DocumentObjectType.TextRange:
                    {
                        TextRange text_range = obj as TextRange;
                        return text_range.Text;
                    }
                case DocumentObjectType.Body:
                    {
                        Body body = obj as Body;
                        return DocConvert.DocToHTML(body);
                    }
                case DocumentObjectType.Picture:
					{
						DocPicture picture = obj as DocPicture;
                        string pic_name = Utils.GetPictruePath(templateConfig.FileSavePath);
                        picture.Image.Save(pic_name);
						return pic_name;
					}
				default:
					{
						return null;
					}
			}
		};

        /// <summary>
        /// 从word模板获取配置信息
        /// </summary>
        /// <param name="fileName">模板文件名</param>
        /// <returns></returns>
		public static WordTemplateConfig GetConfig(string fileName)
		{
            WordTemplateConfig config = new WordTemplateConfig(fileName);
			return config;
		}

        /// <summary>
        /// 通过配置信息获取word文档中的数据
        /// </summary>
        /// <param name="fileName">文件名</param>
        /// <param name="config">配置信息</param>
        /// <returns></returns>
		public static Dictionary<string, string> GetData(string fileName, WordTemplateConfig config)
		{
			return GetData(fileName, config, defaultProcessFunc);
		}

        /// <summary>
        /// 通过配置信息获取word文档中的数据
        /// </summary>
        /// <param name="fileName">文件名</param>
        /// <param name="config">配置信息</param>
        /// <param name="processFunc">自定义处理过程</param>
        /// <returns></returns>
		private static Dictionary<string, string> GetData(string fileName, WordTemplateConfig config, Func<DocumentObject, DocumentObjectConfig, WordTemplateConfig, object> processFunc)
		{
			Document doc = new Document();
			doc.LoadFromFile(fileName, FileFormat.Docx);

            WordHelperRuntime runtime = new WordHelperRuntime
            {
                Doc = doc,
                TemplateConfig = config
            };

            Dictionary<string, string> dic = new Dictionary<string, string>();
            foreach (var c in config.DataKeys)
            {
                KeyConfig key_config = c.Value.KeyConfig;
                DocumentObject obj = GetDocumentObjectByConfig(c.Value.Path.Split(';')[0], c.Value, runtime);

                object value;
                if(obj == null)
                {
                    value = key_config.DefaultValue;
                }
                else
                {
                    switch (c.Value.DocumentObjectType)
                    {
                        case DocumentObjectType.Picture:
                            {
                                foreach (DocumentObject child in obj.ChildObjects)
                                {
                                    if (child.DocumentObjectType == DocumentObjectType.Picture)
                                    {
                                        obj = child;
                                    }
                                }
                                break;
                            }
                        case DocumentObjectType.TextRange:
                            {
                                string text = (obj as Paragraph).Text;
                                int begin = 0;
                                int end = text.Length;
                                if (key_config.ValueBeginMarker != "")
                                {
                                    begin = text.IndexOf(key_config.ValueBeginMarker);
                                    if (begin == -1) begin = 0;
                                    else begin = begin + key_config.ValueBeginMarker.Length;
                                }
                                if (key_config.ValueEndMarker != "")
                                {
                                    end = text.IndexOf(key_config.ValueEndMarker);
                                    if (end == -1) end = 0;
                                }
                                text = text.Substring(begin, end - begin);
                                TextRange text_range = new TextRange(doc);
                                text_range.Text = text;
                                obj = text_range;
                                break;
                            }
                        case DocumentObjectType.Paragraph:
                            {
                                break;
                            }
                        case DocumentObjectType.Body:
                            {
                                break;
                            }
                    }

                    //if (obj.DocumentObjectType != c.Value.DocumentObjectType)
                    if(c.Value.DocumentObjectType == DocumentObjectType.Picture && obj.DocumentObjectType != DocumentObjectType.Picture)
                    {
                        throw new Exception(string.Format("配置属性{0}类型为{1}，实际为{2}", c.Key, c.Value.DocumentObjectType, obj.DocumentObjectType));
                    }
                    value = processFunc(obj, c.Value, config);
                }

                dic.Add(c.Key, value.ToString());
            }
            
            doc.Dispose();
            return dic;
		}

        /// <summary>
        /// 根据配置信息将数据渲染到模板中并生成新的文件
        /// </summary>
        /// <param name="outputFileName">输出文件名</param>
        /// <param name="config">配置信息</param>
        /// <param name="data">数据</param>
		public static void RenderTemplate(string outputFileName, WordTemplateConfig config, Dictionary<string, string> data, WordHelperRuntime runtime = null)
        {
            runtime.SetProgress($"正在加载文件\"{config.FileName}\"");
            Document doc = new Document();
			doc.LoadFromFile(config.FileName);

            if(runtime == null)
            {
                runtime = new WordHelperRuntime();
            }
            runtime.Doc = doc;
            runtime.TemplateConfig = config;
            runtime.Data = data;

            runtime.SetProgress("正在计算变量值");
            Dictionary<string, string> renderedData = GetRenderedData(runtime);

            runtime.SetProgress(0, "开始替换模板变量");
            int i = 0;
            foreach (var c in config.DataKeys)
            {
                string value = renderedData[c.Key];
                DocumentObjectConfig cfg = c.Value;

                if(cfg.Path == "") // 没有元素
                {

                }
                else
                {
                    runtime.SetProgress((int)(100 * ++i / config.DataKeys.Count), $"正在替换模板变量\"{cfg.Name}\"");
                    ReplaceDocumentObject(value, cfg, runtime);
                }
            }

            runtime.SetProgress(100, $"正在保存，保存到\"{outputFileName}\"");
            doc.SaveToFile(outputFileName, FileFormat.Docx);
            doc.Dispose();
            runtime.SetProgress($"保存成功，已保存到\"{outputFileName}\"");
        }

        /// <summary>
        /// 修改模板
        /// </summary>
        /// <param name="fileName">模板文件名</param>
        /// <param name="newFileName">新的模板文件名</param>
        /// <param name="data">替换的值</param>
        public static void ModifyTemplate(string fileName, string newFileName, Dictionary<string, Dictionary<string, string>> data = null)
        {
            WordTemplateConfig config = GetConfig(fileName);
            FileInfo new_file = new FileInfo(newFileName);
            string temp = newFileName.Substring(0, newFileName.IndexOf("."));
            string temp2 = new_file.Name.Substring(0, new_file.Name.IndexOf("."));

            if(data == null)
            {
                data = new Dictionary<string, Dictionary<string, string>>();
            }

            foreach (var k in data)
            {
                if (config.JsonConfig.keys.ContainsKey(k.Key))
                {
                    string value = k.Value["defaultValue"];
                    Dictionary<string, string> key_config = config.JsonConfig.keys[k.Key];
                    if (key_config.ContainsKey("type") && key_config["type"] == "picture")
                    {
                        string pic_value_path = temp2 + "/" + Utils.GetRandomName() + ".png";
                        string pic_save_path = new_file.DirectoryName + "/" + pic_value_path;
                        string pic_save_dir = new_file.DirectoryName + "/" + temp2;
                        if (!Directory.Exists(pic_save_dir))
                        {
                            Directory.CreateDirectory(pic_save_dir);
                        }
                        File.Copy(Utils.GetRelativeFilePath(fileName, value), pic_save_path);
                        value = pic_value_path;
                    }
                    key_config["defaultValue"] = value;
                }
                else
                {
                    throw new Exception(string.Format("不存在变量\"{0}\"", k.Key));
                }
            }

            File.Copy(fileName, newFileName, true);
            string json = JsonConvert.SerializeObject(config.JsonConfig, Formatting.Indented);
            Utils.CreateTextFile(temp + ".json", json);
        }

        private static Dictionary<string, string> GetRenderedData(WordHelperRuntime runtime)
        {
            Dictionary<string, string> temp = new Dictionary<string, string>();
            foreach(var k in runtime.TemplateConfig.DataKeys)
            {
                GetKeyValue(k.Key, temp, runtime);
            }
            return temp;
        }

        private static string GetKeyValue(string key, Dictionary<string,string> renderedData, WordHelperRuntime runtime)
        {
            if(renderedData.ContainsKey(key))
            {
                return renderedData[key];
            }

            runtime.SetProgress($"正在计算变量\"{key}\"的值");
            Dictionary<string, DocumentObjectConfig> keys = runtime.TemplateConfig.DataKeys;
            string value = "";

            // 如果是个函数则执行函数，否则是个值
            string[] arr = key.Split('/');
            if(arr.Length > 1)
            {
                // 期望是个函数
                if (keys.ContainsKey(arr[0]))
                {
                    string funcName = keys[arr[0]].KeyConfig.OutputFunc;
                    if (funcName != "")
                    {
                        // 说明是个函数
                        List<string> values = new List<string>();
                        for (int i = 1; i < arr.Length; i++)
                        {
                            if (arr[i].Length > 0 && arr[i].First() == runtime.TemplateConfig.DataKeyMarker)
                            {
                                // 如果仍然是个变量
                                values.Add(GetKeyValue(arr[i].Substring(1), renderedData, runtime));
                            }
                            else
                            {
                                values.Add(arr[i]);
                            }
                        }
                        value = RunBuiltInFunc(funcName, values.ToArray(), runtime);
                    }
                    else
                    {
                        throw new Exception(string.Format("\"{0}\"为变量而非函数", arr[0]));
                    }
                }
                else
                {
                    throw new Exception(string.Format("不存在名为\"{0}\"的自定义函数", arr[0]));
                }
            }
            else
            {
                DocumentObjectConfig cfg = keys[key];
                if (cfg.KeyConfig.ValueFrom == "")
                {
                    if (runtime.Data.ContainsKey(key))
                    {
                        value = runtime.Data[key];
                    }
                    else
                    {
                        value = cfg.KeyConfig.DefaultValue;
                    }
                }
                else
                {
                    value = GetKeyValue(cfg.KeyConfig.ValueFrom, renderedData, runtime);
                }
            }

            if (!renderedData.ContainsKey(key))
            {
                // 如果还不存在值，则添加进去
                renderedData.Add(key, value);
            }

            return renderedData[key];
        }

        private static string RunBuiltInFunc(string funcName, string[] paramValues, WordHelperRuntime runtime)
        {
            return runtime.TemplateConfig.BuiltInFuncs.RunFunc(funcName, paramValues);
        }


		private static DocumentObject GetDocumentObjectByConfig(string path, DocumentObjectConfig config, WordHelperRuntime runtime)
		{
            if (path == "") return null;

			object obj = null;

			string[] items = path.Split(',');
			foreach(string item in items)
			{
				string[] keyvalue = item.Split(':');
				if (keyvalue.Length != 2) continue;

				int index = int.Parse(keyvalue[0]);
				string type_name = keyvalue[1];

				switch(type_name)
				{
					case "section":
						{
							obj = runtime.Doc.Sections[index].Body;
							break;
                        }
                    case "textbox":
                        {
                            obj = runtime.Doc.TextBoxes[index].Body;
                            break;
                        }
                    case "para":
						{
							obj = (obj as Body).Paragraphs[index];
							break;
						}
					case "table":
						{
							obj = (obj as Body).Tables[index];
							break;
						}
					case "row":
						{
							obj = (obj as Table).Rows[index];
							break;
						}
					case "cell":
						{
							obj = (obj as TableRow).Cells[index] as Body;
							break;
                        }
                    case "header":
                        {
                            obj = runtime.Doc.Sections[index].HeadersFooters.Header;
                            break;
                        }
                    case "footer":
                        {
                            obj = runtime.Doc.Sections[index].HeadersFooters.Footer;
                            break;
                        }
                }
			}

            if (obj == null)
            {
                throw new Exception(string.Format("未匹配到{0}对应的元素", config.Name));
            }

            return obj as DocumentObject;
		}

        private static void ReplaceDocumentObject(string value, DocumentObjectConfig config, WordHelperRuntime runtime)
        {
            string[] paths = config.Path.Split(';');
            foreach (string path in paths)
            {
                if (path == "") continue;
                switch (config.DocumentObjectType)
                {
                    case DocumentObjectType.Paragraph:
                        {
                            ReplaceParagraph(path, value, config, runtime);
                            break;
                        }
                    case DocumentObjectType.TextRange:
                        {
                            ReplaceTextRange(path, value, config, runtime);
                            break;
                        }
                    case DocumentObjectType.Body:
                        {
                            ReplaceBody(path, value, config, runtime);
                            break;
                        }
                    case DocumentObjectType.Picture:
                        {
                            ReplacePicture(path, value, config, runtime);
                            break;
                        }
                }
            }
        }
		// 替换Body
		private static void ReplaceBody(string path, string value, DocumentObjectConfig config, WordHelperRuntime runtime)
        {
            Body body = GetDocumentObjectByConfig(path, config, runtime) as Body;
            DocConvert.HTMLToDoc(body, value);
            
        }
        // 替换文本
        private static void ReplaceTextRange(string path, string value, DocumentObjectConfig config, WordHelperRuntime runtime)
        {
            Paragraph para = GetDocumentObjectByConfig(path, config, runtime) as Paragraph;
            var children = para.ChildObjects;
            
            bool hasKey = false;
            foreach (DocumentObject child in children)
            {
                if (child.DocumentObjectType == DocumentObjectType.TextRange)
                {
                    TextRange textRange = child as TextRange;
                    if (textRange.CharacterFormat.UnderlineStyle == UnderlineStyle.Single)
                    {
                        if (hasKey)
                        {
                            textRange.Text = "";
                        }
                        else
                        {
                            if (textRange.Text.Length > 0 && textRange.Text.First() == runtime.TemplateConfig.DataKeyMarker)
                            {
                                if(GetKeyName(textRange) == config.Name)
                                {   // 说明找到了key
                                    hasKey = true;
                                    textRange.CharacterFormat.UnderlineStyle = UnderlineStyle.None;
                                    DocConvert.HTMLToDoc(textRange, value);
                                    value = textRange.Text.Trim();
                                    if (config.KeyConfig.Length != 0)
									{   
										// 有长度限制
										int len = config.KeyConfig.Length;
										if(value.Length > len)
										{
											// 过长截取
											value = value.Substring(0, len);
										}
										else
										{
											// 过短添加空格，并考虑左中右浮动
											string flt = config.KeyConfig.Float;
											int temp = len - value.Length;
											string spaces = "";
											for(int i = 0; i < temp; i++)
											{
												spaces += " ";
											}
											switch(flt)
											{
												case "left":
													{
														value = value + spaces;
														break;
													}
												case "right":
													{
														value = spaces + value;
														break;
													}
												case "center":
													{
														int half = (int)(temp / 2);
														value = spaces.Substring(0, half) + value + spaces.Substring(half);
														break;
													}
											}
										}

									}
                                    textRange.Text = value;
                                }
                            }
                        }
                    }
                    else
                    {
                        hasKey = false;
                    }
                }
            }
        }

        private static string GetKeyName(TextRange textRange)
        {
            string key = "";
            IDocumentObject obj = textRange;
            while(obj != null)
            {
                if(obj.DocumentObjectType == DocumentObjectType.TextRange)
                {
                    TextRange temp = obj as TextRange;
                    if(temp.CharacterFormat.UnderlineStyle == UnderlineStyle.Single)
                    {
                        key += (obj as TextRange).Text;
                    }
                    else
                    {
                        break;
                    }
                }
                obj = obj.NextSibling;
            }
            key = key.Trim().Substring(1);
            return key;
        }

        // 替换段落
        private static void ReplaceParagraph(string path, string value, DocumentObjectConfig config, WordHelperRuntime runtime)
		{
			Paragraph para = GetDocumentObjectByConfig(path, config, runtime) as Paragraph;
            para.BreakCharacterFormat.UnderlineStyle = UnderlineStyle.None;
            DocConvert.HTMLToDoc(para, value);

		}
		// 替换图片
		private static void ReplacePicture(string path, string value, DocumentObjectConfig config, WordHelperRuntime runtime)
        {
            Paragraph para = GetDocumentObjectByConfig(path, config, runtime) as Paragraph;
            para.Text = "";

            FileStream fs = File.OpenRead(value); //OpenRead
            int filelength = 0;
            filelength = (int)fs.Length; //获得文件长度 
            Byte[] image = new Byte[filelength]; //建立一个字节数组 
            fs.Read(image, 0, filelength); //按字节流读取 
            //System.Drawing.Image result = System.Drawing.Image.FromStream(fs);
            fs.Close();

            DocPicture doc_pic = para.AppendPicture(image);
            if(config.KeyConfig.Height != 0)
            {
                doc_pic.Height = config.KeyConfig.Height;
            }
            if (config.KeyConfig.Width != 0)
            {
                doc_pic.Width = config.KeyConfig.Width;
            }
        }
    }

    public class WordHelperRuntime
    {
        public Document Doc { get; set; }
        public WordTemplateConfig TemplateConfig { get; set; }
        public Dictionary<string, string> Data { get; set; }
        public CallbackHelper<string> ProgressCallback { get; set; } // 注册的进度回调函数

        public string ProgressMessage { get; private set; }
        public int ProgressValue { get; private set; }

        public WordHelperRuntime()
        {
            ProgressMessage = "";
            ProgressValue = 0;
            ProgressCallback = new CallbackHelper<string>();
        }

        public void SetProgress(int value, string message = "")
        {
            ProgressValue = value;
            if(message != "")
            {
                ProgressMessage = message;
            }
            ProgressCallback.Publish(GetProgressTip());
        }

        public void SetProgress(string message)
        {
            ProgressMessage = message;
            ProgressCallback.Publish(GetProgressTip());
        }

        public string GetProgressTip()
        {
            return $"{ProgressMessage},进度：{ProgressValue}%";
        }
    }
}